// Copyright (c) 2009-2011, Tor M. Aamodt
// The University of British Columbia
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution. Neither the name of
// The University of British Columbia nor the names of its contributors may be
// used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

#ifndef MC_PARTITION_INCLUDED
#define MC_PARTITION_INCLUDED

#include "hardware_model.h"
#include "dram.h"

#include <list>
#include <queue>

class MemFetch;

class PartitionMfAllocator : public MemFetchAllocator {
 public:
  PartitionMfAllocator(const MemoryConfig *config) {
    m_memory_config = config;
  }
  virtual MemFetch *alloc( const MemAccess &access,
                           uint64_t cycle) const {
    abort();
    return NULL;
  }
  virtual MemFetch *alloc( addr_t addr, MemAccessType type,
                           uint32_t size, bool wr,
                           uint64_t cycle) const;

 private:
  const MemoryConfig *m_memory_config;
};

// Memory partition unit contains all the units assolcated with a single DRAM
// channel.
// - It arbitrates the DRAM channel among multiple sub partitions.
// - It does not connect directly with the interconnection network.
class MemoryPartitionUnit {
 public:
  MemoryPartitionUnit(uint32_t partition_id, MemoryConfig *config,
                      class SIMULATOR *simulator,
                      class Ramulator *ramulator_wrapper);
  ~MemoryPartitionUnit();

  bool busy() const;

  void cache_cycle(uint32_t cycle);
  void dram_cycle();
  void simple_dram_model_cycle();

  void set_done(MemFetch *mf);

  void print(FILE *fp) const;

  class MemorySubPartition *get_sub_partition(int sub_partition_id) {
    return m_sub_partition[sub_partition_id];
  }


  int global_sub_partition_id_to_local_id(int global_sub_partition_id) const;

  uint32_t get_mpid() const { return m_id; }

  class SIMULATOR *get_simulator() const {
    return m_simulator;
  }

 private:
  uint32_t m_id;
  MemoryConfig *m_config;
  class MemorySubPartition **m_sub_partition;
  class Dram *m_dram;
  uint32_t return_q_size;
  uint32_t sched_q_size;
  class Ramulator *m_ramulator_wrapper;

  class ArbitrationMetadata {
   public:
    ArbitrationMetadata(const MemoryConfig *config);

    // check if a subpartition still has credit
    bool has_credits(int inner_sub_partition_id) const;
    // borrow a credit for a subpartition
    void borrow_credit(int inner_sub_partition_id);
    // return a credit from a subpartition
    void return_credit(int inner_sub_partition_id);

    // return the last subpartition that borrowed credit
    int last_borrower() const { return m_last_borrower; }

    void print(FILE *fp) const;

   private:
    // id of the last subpartition that borrowed credit
    int m_last_borrower;

    int m_shared_credit_limit;
    int m_private_credit_limit;

    // credits borrowed by the subpartitions
    std::vector<int> m_private_credit;
    int m_shared_credit;
  };
  ArbitrationMetadata m_arbitration_metadata;

  // determine wheither a given subpartition can issue to DRAM
  bool can_issue_to_dram(int inner_sub_partition_id);

  // model DRAM access scheduler latency (fixed latency between L2 and DRAM)
  struct dram_delay_t {
    uint64_t ready_cycle;
    class MemFetch *req;
  };
  std::list<dram_delay_t> m_dram_latency_queue;

  class SIMULATOR *m_simulator;
};

class MemorySubPartition {
 public:
  MemorySubPartition(uint32_t sub_partition_id, MemoryConfig *config,
                     class SIMULATOR *simulator);
  ~MemorySubPartition();

  uint32_t get_id() const { return m_id; }

  bool busy() const;

  void cache_cycle(uint32_t cycle);

  bool full() const;
  bool full(uint32_t size) const;
  void push(class MemFetch *mf, uint64_t clock_cycle);
  class MemFetch *pop();
  class MemFetch *top();
  void set_done(MemFetch *mf);

  uint32_t flushL2();
  uint32_t invalidateL2();

  // interface to L2_dram_queue
  bool L2_dram_queue_empty() const;
  class MemFetch *L2_dram_queue_top() const;
  void L2_dram_queue_pop();

  // interface to dram_L2_queue
  bool dram_L2_queue_full() const;
  void dram_L2_queue_push(class MemFetch *mf);

  void print(FILE *fp) const;
  std::vector<MemFetch *> breakdown_request_to_sector_requests(MemFetch *mf);



  //void force_l2_tag_update(addr_t addr, uint32_t time,
                           //mem_access_sector_mask_t mask) {
    //m_L2cache->force_tag_access(addr, m_memcpy_cycle_offset + time, mask);
    //m_memcpy_cycle_offset += 1;
  //}

 private:
  // data
  uint32_t m_id;  //< the global sub partition ID
  MemoryConfig *m_config;
  class L2Cache *m_L2cache;
  class L2Interface *m_L2interface;
  class SIMULATOR *m_simulator;
  PartitionMfAllocator *m_mf_allocator;

  // model delay of ROP units with a fixed latency
  struct rop_delay_t {
    uint64_t ready_cycle;
    class MemFetch *req;
  };
  std::queue<rop_delay_t> m_rop;

  // these are various FIFOs between units within a memory partition
  FifoPipeline<MemFetch> *m_icnt_L2_queue;
  FifoPipeline<MemFetch> *m_L2_dram_queue;
  FifoPipeline<MemFetch> *m_dram_L2_queue;
  FifoPipeline<MemFetch> *m_L2_icnt_queue;  // L2 cache hit response queue

  class MemFetch *L2dramout;
  uint64_t wb_addr;


  std::set<MemFetch *> m_request_tracker;

  friend class L2Interface;


  // This is a cycle offset that has to be applied to the l2 accesses to account
  // for the cudamemcpy read/writes. We want GPsimulator-Sim to only count cycles for
  // kernel execution but we want cudamemcpy to go through the L2. Everytime an
  // access is made from cudamemcpy this counter is incremented, and when the l2
  // is accessed (in both cudamemcpyies and otherwise) this value is added to
  // the gpsimulator-sim cycle counters.
  uint32_t m_memcpy_cycle_offset;
};

class L2Interface : public MemFetchInterface {
 public:
  L2Interface(MemorySubPartition *unit) { m_unit = unit; }
  virtual ~L2Interface() {}
  virtual bool full(uint32_t size, bool write) const {
    // assume read and write packets all same size
    return m_unit->m_L2_dram_queue->full();
  }
  virtual void push(MemFetch *mf) {
    mf->set_status(IN_PARTITION_L2_TO_DRAM_QUEUE, 0 /*FIXME*/);
    m_unit->m_L2_dram_queue->push(mf);
  }

 private:
  MemorySubPartition *m_unit;
};

#endif
